package net.organizer.ui;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Enumeration;
import java.util.Map;
import java.util.logging.Logger;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.organizer.dao.Domain;
import net.organizer.dao.Org;
import net.organizer.dao.OrgPerson;
import net.organizer.dao.Person;
import net.organizer.ui.actionhandler.ActionHandlerBase;
import net.organizer.ui.actionhandler.GetContextActionHandler;
import net.organizer.ui.actionhandler.LoadSampleDataActionHandler;
import net.organizer.ui.actionhandler.OrgDeleteActionHandler;
import net.organizer.ui.actionhandler.OrgGetListActionHandler;
import net.organizer.ui.actionhandler.OrgGetOneActionHandler;
import net.organizer.ui.actionhandler.OrgPersonDeleteActionHandler;
import net.organizer.ui.actionhandler.OrgPersonPutInsertActionHandler;
import net.organizer.ui.actionhandler.OrgPutCreateActionHandler;
import net.organizer.ui.actionhandler.OrgPutUpdateActionHandler;
import net.organizer.ui.actionhandler.OrgPutUpdateParentActionHandler;
import net.organizer.ui.actionhandler.PersonDeleteActionHandler;
import net.organizer.ui.actionhandler.PersonGetListActionHandler;
import net.organizer.ui.actionhandler.PersonGetOneActionHandler;
import net.organizer.ui.actionhandler.PersonPutCreateActionHandler;
import net.organizer.ui.actionhandler.PersonPutUpdateActionHandler;

import org.json.JSONObject;

import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import com.google.appengine.repackaged.com.google.common.collect.Maps;
import com.googlecode.objectify.ObjectifyService;

/**
 * Handles all dynamic http requests for the application.
 * 
 * @TODO enable class to support multiple handler methods, perhaps with
 *       annotations
 */
public class AjaxDispatcher extends HttpServlet {

	private static final long serialVersionUID = 1L;

	private static final Logger logger = Logger.getLogger(AjaxDispatcher.class.getName());

	enum EActionHandler {
		GetContext(GetContextActionHandler.class), 
		LoadSampleData(LoadSampleDataActionHandler.class), 
		OrgDelete(OrgDeleteActionHandler.class), 
		OrgGetList(OrgGetListActionHandler.class), 
		OrgGetOne(OrgGetOneActionHandler.class), 
		OrgPersonDelete(OrgPersonDeleteActionHandler.class),
		OrgPersonPutInsert(OrgPersonPutInsertActionHandler.class),
		OrgPutCreate(OrgPutCreateActionHandler.class), 
		OrgPutUpdate(OrgPutUpdateActionHandler.class), 		
		OrgPutUpdateParent(OrgPutUpdateParentActionHandler.class),
		PersonDelete(PersonDeleteActionHandler.class), 
		PersonGetList(PersonGetListActionHandler.class),
		PersonGetOne(PersonGetOneActionHandler.class),
		PersonPutCreate(PersonPutCreateActionHandler.class),
		PersonPutUpdate(PersonPutUpdateActionHandler.class);

		private static Map<String, EActionHandler> _actionHandlerMap;
		private Class<? extends ActionHandlerBase> _actionHandlerClass;
		private String _url;
		static {
			_actionHandlerMap = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER);
		}
		private EActionHandler(final Class<? extends ActionHandlerBase> actionHandlerClass) {
			_actionHandlerClass = actionHandlerClass;
			_url = "/" + name().substring(0, 1).toLowerCase() + name().substring(1) + ".app";
		}
		public Class<? extends ActionHandlerBase> getActionHandlerClass() {
			return _actionHandlerClass;
		}
		public String getURL() {
			return _url;
		}

		public static EActionHandler lookupUsingURL(final String url) {
			synchronized (_actionHandlerMap) {
				if (_actionHandlerMap.size() == 0) {
					for (EActionHandler actionHandler : EActionHandler.values()) {
						_actionHandlerMap.put(actionHandler._url, actionHandler);
					}
				}
			}
			return _actionHandlerMap.get(url);
		}
	}

	static {
		ObjectifyService.register(Org.class);
		ObjectifyService.register(OrgPerson.class);
		ObjectifyService.register(Person.class);
		ObjectifyService.register(Domain.class);
	}

	// --------------------------------------------------------------------------------------------------
	@SuppressWarnings({ "rawtypes" })
	public static void logRequest(HttpServletRequest req) {
		logger.finer(String.format("logRequest... req.getAuthType(): %s", req.getAuthType()));
		logger.finer(String.format("logRequest... req.getContextPath(): %s", req.getContextPath()));
		logger.finer(String.format("logRequest... req.getMethod(): %s", req.getMethod()));
		logger.finer(String.format("logRequest... req.getPathIfo(): %s", req.getPathInfo()));
		logger.finer(String.format("logRequest... req.getPathTranslated(): %s", req.getPathTranslated()));
		logger.finer(String.format("logRequest... req.getQueryString(): %s", req.getQueryString()));
		logger.finer(String.format("logRequest... req.getRemoteUser(): %s", req.getRemoteUser()));
		logger.finer(String.format("logRequest... req.getRequestURI(): %s", req.getRequestURI()));
		logger.finer(String.format("logRequest... req.getRequestURL(): %s", req.getRequestURL()));
		logger.finer(String.format("logRequest... req.getServletPath(): %s", req.getServletPath()));
		logger.finer(String.format("logRequest... attributes..."));
		Enumeration attributeNames = req.getAttributeNames();
		while (attributeNames.hasMoreElements()) {
			String attributeName = (String) attributeNames.nextElement();
			Object object = req.getAttribute(attributeName);
			logger.finer(String.format("attributeName: %s, value class name: %s, value toString: %s", attributeName,
					object.getClass().getName(), object.toString()));
		}
		logger.finer(String.format("logRequest... parameters..."));
		Enumeration parameterNames = req.getParameterNames();
		while (parameterNames.hasMoreElements()) {
			String parameterName = (String) parameterNames.nextElement();
			logger.finer(String.format("parameterName: %s", parameterName));
			for (String value : req.getParameterValues(parameterName)) {
				logger.finer(String.format("...parameter value: %s", value));
			}
		}
	}

	// --------------------------------------------------------------------------------------------------

	public void errorResponse(HttpServletResponse resp, String message) throws IOException {
		logger.severe(message);
		resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
		resp.setContentType("text/plain");
		resp.getWriter().println(message);
	}

	// --------------------------------------------------------------------------------------------------
	@Override
	/**
	 * delegates the request to the designated handler.
	 * catches exceptions, returns error information
	 */
	protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		logger.fine("service");
		String errorMessage = null;
		try {
			logRequest(req);
			//Thread.sleep(3000);
			UserService userService = UserServiceFactory.getUserService();
			User user = userService.getCurrentUser();
			String fapUser = req.getHeader("FAPUser");
			if (user == null
					&& (req.getRequestURL().toString().contains("andy-foster-a-pet") || req.getRequestURL().toString()
							.contains("localhost")) && fapUser != null && fapUser.contains("fap_test")) {
				user = new User("fap_test@gmail.com", "");
			}
			if (user == null) {
				logger.fine("user == null, not authenticated");
				resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
				resp.setContentType("text/plain");
				resp.getWriter().println(userService.createLoginURL("/home.html"));
			} else {
				logger.fine("user.getNickname():" + user.getNickname());
				req.setAttribute("user", user);
				String servletPath = req.getServletPath();
				if (servletPath == null) {
					errorMessage = "servletPath is empty";
				} else {
					logger.fine("dispatch...servletPath:" + servletPath);
					EActionHandler actionHandler = EActionHandler.lookupUsingURL(servletPath);
					if (actionHandler == null) {
						errorMessage = "Action handler not found for:" + servletPath;
					} else {
						JSONObject respJSONObject = new JSONObject();
						actionHandler.getActionHandlerClass().newInstance().handleAction(req, respJSONObject);
						resp.setContentType("application/json");
						String message = respJSONObject.toString();
						resp.getWriter().println(message);
						logger.fine(message);
					}
				}
			}
		} catch (Exception e) {
			StringWriter sw = new StringWriter();
			PrintWriter pw = new PrintWriter(sw);
			e.printStackTrace(pw);
			logger.severe(sw.toString());
			errorMessage = sw.toString();
		}
		if (errorMessage != null) {
			errorResponse(resp, errorMessage);
		}
	}

	// --------------------------------------------------------------------------------------------------

}